Skip to content

[codex] Route permission requests through Oracle#858

Merged
malpern merged 1 commit into
masterfrom
codex/permission-request-oracle
Jun 10, 2026
Merged

[codex] Route permission requests through Oracle#858
malpern merged 1 commit into
masterfrom
codex/permission-request-oracle

Conversation

@malpern

@malpern malpern commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Route PermissionRequestService permission-state reads through PermissionOracle.shared before and after prompt attempts.
  • Keep IOHIDRequestAccess and AXIsProcessTrustedWithOptions as prompt side effects only, with immediate Oracle refreshes after.
  • Propagate async permission request calls through wizard/gate callers and document the request/read split in ADR-001.

Closes #854

Validation

  • /opt/homebrew/bin/swiftformat --lint Sources/KeyPathAppKit/Services/Permissions/PermissionRequestService.swift Sources/KeyPathWizardCore/WizardServiceProtocols.swift Sources/KeyPathInstallationWizard/UI/Pages/WizardInputMonitoringPage.swift Sources/KeyPathInstallationWizard/UI/Pages/WizardAccessibilityPage.swift Sources/KeyPathAppKit/Services/Permissions/PermissionGate.swift
  • python3 Scripts/check-accessibility.py
  • git diff --check
  • swift build --jobs 4
  • swift test --jobs 4 --filter PermissionGateEvaluationTests
  • pre-push swift build hook

Notes

  • ./Scripts/test-fast.sh --changed falls back to the full safe suite for this diff and failed in unrelated pack/snapshot tests: five CLIPackCRUDTests failures from PackInstaller.swift:125 (saveFailed("could not enable associated rule collection")) and HardViewSnapshotTests.testPackDetailCapsLockRemap snapshot mismatch. Permission Oracle tests in that run passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e62611984a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let granted = IOHIDRequestAccess(kIOHIDRequestTypeListenEvent)
_ = IOHIDRequestAccess(kIOHIDRequestTypeListenEvent)
let refreshed = await PermissionOracle.shared.forceRefresh()
let granted = refreshed.keyPath.inputMonitoring.isReady

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use IOHIDRequestAccess when TCC is unverifiable

When KeyPath lacks Full Disk Access, the Oracle's Input Monitoring read can stay .unknown because it falls back to TCC database access and returns nil if the DB cannot be read (PermissionOracle.tccStatus(forBundleID:)). In that common wizard state, a user can accept the IOHIDRequestAccess prompt but this line derives granted only from the refreshed Oracle snapshot, so the request returns false and the Input Monitoring page keeps polling and opens System Settings even though the system prompt succeeded. Keep the Oracle read as authoritative when it can verify, but preserve the IOHIDRequestAccess result as a fallback for the unverifiable/unknown case.

Useful? React with 👍 / 👎.

@malpern malpern merged commit 5c343d5 into master Jun 10, 2026
8 of 9 checks passed
@malpern malpern deleted the codex/permission-request-oracle branch June 10, 2026 01:57
@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code Review — PR #858: Route permission requests through Oracle

Overall this is a clean, focused change that correctly enforces the Oracle pattern. The read/write split (prompt APIs as side-effects only, Oracle as the state source of truth) is well-reasoned and the ADR-001 update clearly documents the intent.

Strengths

  • Correct Oracle pattern: Using currentSnapshot() before the prompt and forceRefresh() after is the right split. currentSnapshot() avoids a round-trip when the Oracle is warm; forceRefresh() ensures the return value reflects post-prompt reality.
  • Discarding system API return values with _ clearly communicates they are now prompt side-effects only, not state sources.
  • ADR-001 update is precise — the "write/prompt side effect" vs "state read" framing is a good canonical statement.
  • Protocol updated atomically — no stale conformance lurking somewhere.

Issue 1: Outer Task in wizard pages is unstored — no cancellation on navigation

Both WizardAccessibilityPage.swift and WizardInputMonitoringPage.swift now wrap the entire flow (request call, polling setup, fallback) in an unattached Task { @MainActor in }. The task is not stored in any property, so it cannot be cancelled in onDisappear/view cleanup. The original fallback task had the same gap, but the request call itself was synchronous — here the whole flow, including the System Settings open, runs in the unstored task.

Suggestion: store the outer task handle alongside permissionPollingTask and cancel both on cleanup.

Issue 2: currentSnapshot() pre-check may use stale state

If the Oracle's cached snapshot is stale (e.g., user manually granted in System Settings between the last Oracle refresh and this call), currentSnapshot() returns "not granted" and the code falls through to IOHIDRequestAccess / AXIsProcessTrustedWithOptions. In practice the system dialog won't re-appear (those APIs silently return when already granted), and the subsequent forceRefresh() will catch it. No user-visible problem — but a short comment explaining why currentSnapshot() is preferred over forceRefresh() for the pre-check would make the intent clear to future readers.

Issue 3: Fallback timer starts later than before (minor)

The 1500ms fallback (WizardSleep.ms(1500)) now starts after requestAccessibilityPermission() returns, which itself ends with a forceRefresh(). In the original the 1500ms started immediately after the synchronous call. The Oracle refresh adds a small delay before the "open System Settings" fallback fires. Probably unnoticeable, just worth keeping in mind if the fallback UX ever feels sluggish.

Test coverage gap

The unrelated failures noted in the PR (CLIPackCRUDTests, HardViewSnapshotTests) are pre-existing. Oracle-relevant tests passed. One gap: the new "already granted before prompt" early-return path introduced by the beforePrompt check. A unit test mocking PermissionOracle with a pre-granted snapshot would lock in that behaviour and prevent future regressions.

Summary: The Oracle routing is correct and the approach is sound. The main item worth addressing before merge is the unstored outer Task — it means neither the requestAccessibilityPermission/requestInputMonitoringPermission call nor the System Settings fallback can be cancelled if the wizard page disappears mid-flow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PermissionRequestService bypasses PermissionOracle for AX and IOHID checks

1 participant